package rcs34.android.AndroidRC;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;

import rcs34.android.AndroidRC.Receivers.ARCBroadcastReceiver;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

public class AndroidRCServer extends Service implements Handler.Callback,
    OnSharedPreferenceChangeListener {
  public static final String KEY_MESSENGER = "messenger";
  public static final String KEY_MESSAGE = "msg";
  public static final String KEY_OPERATION = "operation";
  public static final int VAL_START = 1;
  public static final int VAL_STOP = 2;
  public static final int MSG_CLIENT_ADD = 1;
  public static final int MSG_CLIENT_DEL = 2;
  public static final int MSG_CLEAR_NOTIFICATION = 13;
  public static final int MSG_TCP_STARTED = 3;
  public static final int MSG_TCP_STOPPED = 4;
  public static final int MSG_TCP_BIND_FAIL = 6;
  public static final int MSG_CONNECTION_CLOSE = 7;
  public static final int MSG_CONNECTION_OPEN = 8;
  public static final int MSG_STARTING_TCP = 9;
  public static final int MSG_STOPPING_TCP = 11;
  public static final int MSG_NEW_IP_REQUEST = 12;
  public static final int MAX_SOCKETS_COUNT = 10;
  private final String LOGTAG = getClass().getName();
  private HTTPThread httpThread;
  public static int port = 0;
  private boolean checkIP;
  private long inactivityTimeout;
  private ARCBroadcastReceiver batteryReceiver;
  private ARCTelephonyListener telephonyListener;
  private AndroidRCServer aThis;
  private boolean started = false;
  public static int pendingOperations = 0;
  private SharedPreferences sharedPrefs;
  private HashMap<Integer, Messenger> messengers;
  private PowerManager.WakeLock wakeLock;
  private WifiManager.WifiLock wifiLock;

  public void fireChange(int _status, String _message) {
    Message msg = new Message();
    msg.arg1 = _status;
    switch (_status) {
    case MSG_TCP_STARTED:
      pendingOperations--;
      break;
    case MSG_TCP_BIND_FAIL:
      pendingOperations--;
      Bundle failData = new Bundle();
      failData.putString(KEY_MESSAGE, _message);
      msg.setData(failData);
      break;
    case MSG_CONNECTION_CLOSE:
      GApp.socketsOpened--;
      break;
    case MSG_CONNECTION_OPEN:
      GApp.socketsOpened++;
      break;
    case MSG_TCP_STOPPED:
      pendingOperations--;
      started = false;
      break;
    case MSG_STARTING_TCP:
      pendingOperations++;
      break;
    case MSG_STOPPING_TCP:
      pendingOperations++;
      break;
    case MSG_NEW_IP_REQUEST:
      break;
    }
    try {
      if (!messengers.isEmpty()) {
        for (Entry<Integer, Messenger> entry : messengers.entrySet()) {
          entry.getValue().send(msg);
        }
      }
    } catch (RemoteException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private void notifyAuth(String _ip) {
    Intent intent = new Intent(this, Activity_IpAuth.class);
    PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0);
    Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("Auth request from " + _ip)
        .setContentText(GApp.ipRegister.size() + " IP's registered now")
        .setSmallIcon(R.drawable.ic_newip).setContentIntent(pIntent).build();
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    // mId allows you to update the notification later on.
    notificationManager.notify(0, notification);
  }

  @Override
  public void onCreate() {
    // System.out.println("oncreate" + this);
    aThis = this;
    sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    // Listen to pref changes
    sharedPrefs.registerOnSharedPreferenceChangeListener(this);
    messengers = new HashMap<Integer, Messenger>();
  }

  @Override
  public int onStartCommand(Intent _intent, int _flags, int _startId) {
    // System.out.println("Intent: " + _intent);
    if (_intent != null) {
      int operation = _intent.getIntExtra(KEY_OPERATION, VAL_STOP);
      switch (operation) {
      case VAL_START:
        if (!started) {
          // System.out.println("Starting: ");
          started = true;
          batteryReceiver = new ARCBroadcastReceiver();
          registerReceiver(batteryReceiver, new IntentFilter(
              Intent.ACTION_BATTERY_CHANGED));
          telephonyListener = new ARCTelephonyListener();
          GApp.ipRegister.clear();
          // wifiLock.acquire();
          // Read preferences
          String sPort = sharedPrefs.getString(
              GApp.resources.getString(R.string.prefkeyport), "");
          try {
            long lPort = Long.parseLong(sPort);
            port = (int) Math.min(lPort, 65355);
          } catch (NumberFormatException e) {
            port = Integer.parseInt(GApp.resources
                .getString(R.string.prefporthint));
          }
          checkIP = sharedPrefs.getBoolean(
              GApp.resources.getString(R.string.prefkeycheckip), false);
          inactivityTimeout = Long.parseLong(sharedPrefs.getString(
              GApp.resources.getString(R.string.prefkeyinactivity), "60000"));
          // Start server
          fireChange(MSG_STARTING_TCP, null);
          httpThread = new HTTPThread();
          httpThread.start();
        }
        break;
      case VAL_STOP:
        // System.out.println("Stopping: ");
        stopServerWork();
      }
    }
    return START_STICKY;
  }

  synchronized private void stopServerWork() {
    sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
    if (batteryReceiver != null) {
      unregisterReceiver(batteryReceiver);
      batteryReceiver = null;
    }
    if (telephonyListener != null) {
      telephonyListener.unregister();
      telephonyListener = null;
    }
    if (httpThread != null) {
      httpThread.cancel();
      httpThread = null;
    }
  }

  @Override
  public void onDestroy() {
    // System.out.println("Ondestroy");
    stopServerWork();
  }

  @Override
  public IBinder onBind(Intent arg0) {
    // System.out.println("Onbind");
    return new Messenger(new Handler(this)).getBinder();
  }

  private class HTTPThread extends Thread {
    private ServerSocket serverSocket;
    private boolean stopped = false;
    private final Object SOCKET_MONITOR = new Object();

    @Override
    public void run() {
      PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
      wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
          aThis.getPackageName());
      wakeLock.acquire();
      WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
      wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF,
          aThis.getPackageName());
      wifiLock.acquire();
      setInactivityTimeout();
      try {
        serverSocket = new ServerSocket(port);
        fireChange(MSG_TCP_STARTED, null);
      } catch (IOException e) {
        fireChange(MSG_TCP_BIND_FAIL, "TCP bind failed: " + e.getMessage());
        e.printStackTrace();
        stopHttpWork();
      }
      try {
        while (!stopped) {
          Socket socket = serverSocket.accept();
          if (!stopped) {
            HttpProcessor processor = new HttpProcessor(socket, aThis);
            if (checkIP) {
              String ip = socket.getInetAddress().getHostAddress().trim();
              Boolean enabled = GApp.ipRegister.get(ip);
              if (enabled == null) {
                GApp.ipRegister.put(ip, false);
                fireChange(MSG_NEW_IP_REQUEST, ip);
                notifyAuth(ip);
                processor.forbidden();
                continue;
              } else {
                if (enabled == false) {
                  processor.forbidden();
                  continue;
                }
              }
            }
            processor.start();
          }
        }
      } catch (IOException e) {
        if (!stopped) {// Normal if stoppedallowed
          e.printStackTrace();
        }
      } finally {
        stopHttpWork();
      }
    }

    private void stopHttpWork() {
      stopped = true;
      GApp.inactivity.cancel();
      synchronized (SOCKET_MONITOR) {
        if (wakeLock != null && wakeLock.isHeld()) {
          wakeLock.release();
        }
        if (wifiLock != null && wifiLock.isHeld()) {
          wifiLock.release();
        }
        try {
          if (serverSocket != null) {
            serverSocket.close();
          }
        } catch (IOException e) {
          // Obvious, ignore
        } finally {
          fireChange(MSG_TCP_STOPPED, null);
        }
      }
    }

    public void cancel() {
      fireChange(MSG_STOPPING_TCP, null);
      stopHttpWork();
    }
  }

  @Override
  public boolean handleMessage(Message _msg) {
    // System.out.println("handleMessage " + _msg);
    switch (_msg.arg1) {
    case MSG_CLIENT_ADD:
      messengers.put(_msg.arg2, _msg.replyTo);
      if (started) {
        fireChange(MSG_TCP_STARTED, null);
      } else {
        fireChange(MSG_TCP_STOPPED, null);
      }
      break;
    case MSG_CLIENT_DEL:
      messengers.remove(_msg.arg2);
      break;
    case MSG_CLEAR_NOTIFICATION:
      NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      // mId allows you to update the notification later on.
      notificationManager.cancel(0);
      break;
    }
    return false;
  }

  private void setInactivityTimeout() {
    if (inactivityTimeout > 0L) {
      GApp.inactivity.setUp(new TimerTask() {
        public void run() {
          //System.out.println("Going down");
          stopServerWork();
        }
      }, inactivityTimeout);
    }
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
      String key) {
    checkIP = sharedPrefs.getBoolean(
        GApp.resources.getString(R.string.prefkeycheckip), true);
    setInactivityTimeout();
  }
}
